#include <iostream>
#include <filesystem>
#include <vector>
#include <array>
#include <regex>
#include <cstdlib>
#include <algorithm>
#include <fstream>

namespace fs = std::filesystem;
constexpr int MAX_CH = 192;

#pragma pack(push)
#pragma pack(1)
typedef struct
{
    uint32_t    timestamp_s;
    uint32_t    timestamp_us;
    uint16_t    step_index;
    uint16_t    proc_cycle;
    uint16_t    step_cycle;
    uint32_t    run_time;
    uint8_t     status;
    uint8_t     mode;
    uint8_t     flag;
    uint8_t     ign_status;
    float       voltage;
    float       current;
    float       capacity;
    float       power;
    float       bat_temp;
    float       dcr;
    float       probe_vol;
    float       dcir;
    float       line_vol;
    float       reserve0;
    float       reserve1;
    float       reserve2;
    float       reserve3;
    float       reserve4;
} ch_data_t;
#pragma pack(pop)

static inline int u32_to_str(uint32_t u_data, char* buf)
{
    int len = 0;
    do {
        buf[len] = (u_data % 10) + '0';
        u_data /= 10;
        len++;
    } while (u_data > 0);

    // order swap
    for (int i = 0; i < len / 2; i++)
    {
        char temp = buf[i];
        buf[i] = buf[len - 1 - i];
        buf[len - 1 - i] = temp;
    }
    buf[len] = 0;
    return len;
}

static inline int i32_to_str(int32_t i_data, char* buf)
{
    int len = 0;
    if (i_data < 0)
    {
        buf[len++] = '-';
        i_data = -i_data;
    }
    len += u32_to_str(i_data, buf + len);
    buf[len] = 0;
    return len;
}

static inline int f32_to_str(float f_data, char* buf)
{
    int len = 0;
    if (f_data < 0)
    {
        f_data = -f_data;
        buf[len++] = '-';
    }
    uint32_t x = (uint32_t)f_data;
    uint32_t y = (uint32_t)((f_data - x) * 1000);
    len += u32_to_str(x, buf + len);
    if (y > 0)
    {
        buf[len++] = '.';
        buf[len++] = (y / 100) + '0';
        y %= 100;
        buf[len++] = (y / 10) + '0';
        buf[len++] = (y % 10) + '0';
    }
    buf[len] = 0;
    return len;
}

static inline int t32_to_str(time_t t_data, char* buf)
{
    struct tm *local_tm = localtime(&t_data);
    return (int)strftime(buf, 80, "'%Y-%m-%d %H:%M:%S", local_tm);
}

static int ch_data_to_str(const ch_data_t* data, char str[])
{
    int len = 0;

#define DATA_PROC(type, x, comma)               \
    len += type##_to_str(data->x, str + len);   \
    str[len++] = comma

    DATA_PROC(t32, timestamp_s, ',');
    DATA_PROC(u32, step_index, ',');
    DATA_PROC(u32, proc_cycle, ',');
    DATA_PROC(u32, step_cycle, ',');
    DATA_PROC(u32, run_time, ',');
    DATA_PROC(u32, status, ',');
    DATA_PROC(u32, mode, ',');
    DATA_PROC(u32, flag, ',');
    DATA_PROC(u32, ign_status, ',');
    DATA_PROC(f32, voltage, ',');
    DATA_PROC(f32, current, ',');
    DATA_PROC(f32, capacity, ',');
    DATA_PROC(f32, power, ',');
    DATA_PROC(f32, bat_temp, ',');
    DATA_PROC(f32, dcr, ',');
    DATA_PROC(f32, probe_vol, ',');
    DATA_PROC(f32, dcir, ',');
    DATA_PROC(f32, line_vol, ',');
    DATA_PROC(f32, reserve0, ',');
    DATA_PROC(f32, reserve1, ',');
    DATA_PROC(f32, reserve2, ',');
    DATA_PROC(f32, reserve3, ',');
    DATA_PROC(f32, reserve4, '\n');
#undef DATA_PROC

    str[len] = 0;
    return len;
}

class CsvGenerator
{
public:
    const std::string out_path;
    CsvGenerator(int ch = -1);
    int gen_csv(void);

private:
    const int ch_idx;
    const char* const csv_header =
        "timestamp,step_index,proc_cycle,step_cycle,run_time,"
        "status,mode,flag,ign_status,voltage,current,capacity,"
        "power,bat_temp,dcr,probe_vol,dcir,line_vol,reserve0,"
        "reserve1,reserve2,reserve3,reserve4\n";
    std::array<std::vector<std::string>, MAX_CH> files;
    int gen_one(int ch);
};

CsvGenerator::CsvGenerator(int ch):
    out_path("output"), ch_idx((ch>=0 && ch<MAX_CH) ? ch : -1)
{
    std::string pat("^.+_ch");
    if (ch_idx >= 0)
    {
        pat += std::to_string(ch);
    }
    else
    {
        pat += "(\\d{1,3})";
        // pat = "^.+_ch(\\d{1,3})\\.bin$";
    }
    pat += "\\.bin$";

    std::regex pattern(pat);
    fs::recursive_directory_iterator begin("."), end;
    for (; begin != end; ++ begin)
    {
        if (!fs::is_directory(begin->path()))
        {
            std::smatch match;
            std::string path = begin->path().string();
            if (std::regex_match(path, match, pattern))
            {
                if (ch_idx >= 0)
                {
                    files[ch_idx].emplace_back(path);
                }
                else
                {
                    int n = std::stoi(match.str(1));
                    if (n >= 0 && n < MAX_CH)
                    {
                        files[n].emplace_back(path);
                    }
                }
            }
        }
    }

    fs::path out_dir_path(out_path);
    if (fs::exists(out_dir_path))
    {
        std::cout << "remove dir: " << out_dir_path << std::endl;
        fs::remove_all(out_dir_path);
    }
    fs::create_directory(out_dir_path);
    for(auto &a: files)
    {
        if (a.size() > 0)
        {
            std::sort(a.begin(), a.end());
        }
    }
    std::cout << "prepare done!" << std::endl;
}

int CsvGenerator::gen_one(int ch)
{
    if (ch >= MAX_CH || files[ch].size() == 0)
    {
        return -1;
    }

    std::ofstream out(out_path + "/ch" + std::to_string(ch) + ".csv");
    if (!out.is_open())
    {
        return -2;
    }

    /* write header */
    out.write(csv_header, strnlen(csv_header, 512));

    /* write content */
    for (const auto &file : files[ch])
    {
        std::ifstream in(file, std::ifstream::binary);
        if (!in.is_open())
        {
            return -3;
        }
        while (!in.eof())
        {
            char read_buff[sizeof(ch_data_t)];
            auto read_num = in.read(read_buff, sizeof(read_buff)).gcount();
            if (read_num == 0)
            {
                /* do nothing */
                continue;
            }
            if (read_num != sizeof(read_buff))
            {
                return -4;
            }
            ch_data_t *ch_data = reinterpret_cast<ch_data_t *>(read_buff);
            char write_buf[512];
            auto write_len = ch_data_to_str(ch_data, write_buf);
            out.write(write_buf, write_len);
        }
    }
    return 0;
}

int CsvGenerator::gen_csv(void)
{
    int beg, end;
    if (ch_idx >= 0)
    {
        beg = ch_idx;
        end = ch_idx + 1;
    }
    else
    {
        beg = 0;
        end = MAX_CH;
    }
    for (int i = beg; i < end; i ++)
    {
        auto ret = gen_one(i);
        if (ret < -1)
        {
            std::cout << "ch" << i << " error:" << ret << std::endl;
        }
        else if (!ret)
        {
            std::cout << "ch" << i << " done!" << std::endl;
        }
    }
    return 0;
}

static void scan_files(const fs::path& path, 
    std::vector<std::string>& files, bool recursive = false)
{
    if (!fs::exists(path))
    {
        return;
    }
    fs::directory_iterator dir(path);
    for (const auto& it : dir)
    {
        if (it.is_directory())
        {
            if (recursive)
            {
                scan_files(it.path(), files, true);
            }
        }
        else
        {
            files.emplace_back(it.path().string());
        }
    }
}

int main(int argc, char *argv[])
{
    int ch = (argc == 2)? std::atoi(argv[1]) : -1;

    std::cout << "begin..." << std::endl;
    CsvGenerator(ch).gen_csv();
    std::cout << "end !!!" << std::endl;

    (void)std::getchar();
    return 0;
}
